package atom

import (
	"os"
	"fmt"
	"path/filepath"
	"kumachan/lang/textual/cst"
	"kumachan/lang/textual/scanner"
	"kumachan/interpreter/compiler/loader"
	"kumachan/interpreter/compiler/checker"
	"kumachan/lang/source"
	"kumachan/lang/textual/transformer"
)


type LintRequest struct {
	Path            string      `json:"path"`
	VisitedModules  [] string   `json:"visitedModules"`
}

type LintResponse struct {
	Module   string         `json:"module"`
	Errors   [] LintError   `json:"errors"`
}

type LintError struct {
	Severity     string         `json:"severity"`
	Location     LintLocation   `json:"location"`
	Excerpt      string         `json:"excerpt"`
	Description  string         `json:"description"`
}

type LintLocation struct {
	File      string   `json:"file"`
	Position  Range    `json:"position"`
}

type Range struct {
	Start  Point   `json:"start"`
	End    Point   `json:"end"`
}

type Point struct {
	Row  int   `json:"row"`
	Col  int   `json:"column"`
}

func GetPoint(point scanner.Point) Point {
	var row int
	var col int
	row = (point.Row - 1)
	if point.Col == 0 {
		col = 1
	} else {
		col = (point.Col - 1)
	}
	return Point { row, col }
}

func GetLocation(file string, info scanner.RowColInfo, span scanner.Span) LintLocation {
	if span == (scanner.Span {}) {
		// empty span
		return LintLocation {
			File:     file,
			Position: Range {
				Start: Point { 0, 0 },
				End:   Point { 0, 1 },
			},
		}
	} else {
		var start = info[span.Start]
		var end = info[span.End]
		return LintLocation {
			File:     file,
			Position: Range {
				Start: GetPoint(start),
				End:   GetPoint(end),
			},
		}
	}
}

func AdaptLocation(loc source.Location) LintLocation {
	var f, is_text = loc.File.(*transformer.File)
	if is_text {
		var span = scanner.Span {
			Start: int(loc.Pos.Index1),
			End:   int(loc.Pos.Index2),
		}
		return GetLocation(f.Path, f.Info, span)
	} else {
		return LintLocation {
			File: loc.FilePath(),
		}
	}
}

func GetError(e *source.Error, tip string) LintError {
	var msg = e.ToLocatable()
	var loc = msg.Location
	var desc = msg.Content
	return LintError {
		Severity:    "error",
		Location:    AdaptLocation(loc),
		Excerpt:     desc.RenderPlainText(),
		Description: tip,
	}
}

func Lint(req LintRequest, ctx LangServerContext) LintResponse {
	var dir = filepath.Dir(req.Path)
	var manifest_path = filepath.Join(dir, loader.ManifestFileName)
	for _, visited := range req.VisitedModules {
		if dir == visited || req.Path == visited {
			return LintResponse {}
		}
	}
	var mod_path string
	var mf, err_mf = os.Open(manifest_path)
	if err_mf != nil {
		mod_path = req.Path
	} else {
		mod_path = dir
		_ = mf.Close()
	}
	// ctx.DebugLog("Lint Path: " + mod_path)
	var mod, _, err_loader = loader.LoadEntry(mod_path)
	if err_loader != nil {
		var e, ok = err_loader.ToSourceError()
		if ok {
			return LintResponse {
				Module: mod_path,
				Errors: [] LintError { GetError(e, "") },
			}
		} else {
			switch e := err_loader.Concrete.(type) {
			case loader.E_ParseFailed:
				var desc = e.ParserError.Desc()
				var tree = e.ParserError.Tree
				var index = e.ParserError.NodeIndex
				var token = cst.GetNodeFirstToken(tree, index)
				var span = token.Span
				var err = LintError {
					Severity: "error",
					Location: GetLocation(tree.Name, tree.Info, span),
					Excerpt:  desc.RenderPlainText(),
				}
				return LintResponse {
					Module: mod_path,
					Errors: [] LintError { err },
				}
			default:
				// var desc = err_loader.Desc()
				// ctx.DebugLog(mod_path + " unable to lint: " + desc.StringPlain())
				return LintResponse {
					Module: mod_path,
				}
			}
		}
	}
	var _, errs_checker = checker.TypeCheck(mod)
	if errs_checker != nil {
		var errs = make([] LintError, 0)
		for _, e := range errs_checker {
			var none_callable, is = e.Content.(checker.E_InvalidFunctionUsage)
			if is {
				for _, c := range none_callable.Candidates {
					var tip = fmt.Sprintf(
						"(overloaded candidate %s: %s)", c.Name, c.Signature)
					errs = append(errs, GetError(c.Error, tip))
				}
				continue
			}
			errs = append(errs, GetError(e, ""))
		}
		return LintResponse {
			Module: mod_path,
			Errors: errs,
		}
	}
	return LintResponse {
		Module: mod_path,
	}
}


